package servlet;

import annotation.*;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import lombok.NonNull;
import org.apache.log4j.Logger;
import pojo.Handler;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.net.URL;
import java.util.*;
import java.util.regex.Pattern;

/**
 * @Description simple-mvc 前端控制器，用于请求处理
 * @Date 2020/12/9 14:09
 * @Author zheng9feng
 */
public class DispatcherServlet extends HttpServlet {
	
	private static final Logger LOGGER = Logger.getLogger(DispatcherServlet.class);
	
	/**
	 * 封装配置文件内容
	 */
	private Properties properties = new Properties();
	
	/**
	 * 用于存放包扫描得到的全路径类名
	 */
	private List<String> classTypeList = new ArrayList<>();
	
	/**
	 * 用于存放实例化和完成依赖注入的bean
	 */
	private Map<String, Object> beans = new HashMap<>();
	
	/**
	 * 处理器集合
	 */
	private List<Handler> handlerList = new ArrayList<>();
	
	@Override
	public void init(ServletConfig config) throws ServletException {
		LOGGER.info("init...");
		String contextConfigLocation = config.getInitParameter("contextConfigLocation");
		// 加载配置文件:properties
		try {
			doLoadConfig(contextConfigLocation);
		} catch (IOException e) {
			System.out.println("加载配置文件失败");
			e.printStackTrace();
		}
		
		// 包扫描，获取目标bean
		try {
			doPackageScan(properties.getProperty("basePackage"));
		} catch (Exception e) {
			e.printStackTrace();
		}
		// 初始化bean
		try {
			doInit();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
		// 依赖注入
		try {
			doAutowired();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
		// 处理器映射器配置
		doInitHandlerMapping();
		LOGGER.info("initial finished");
	}
	
	/**
	 * 处理器映射器配置:保存请求路径和处理器方法的映射关系
	 */
	private void doInitHandlerMapping() {
		if (beans.isEmpty()) {
			return;
		}
		
		for (Map.Entry<String, Object> entry : beans.entrySet()) {
			Object beanInstance = entry.getValue();
			Class<?> beanClass = beanInstance.getClass();
			
			// 处理controller的bean
			if (beanClass.isAnnotationPresent(Controller.class)) {
				RequestMapping controllerRequestMapping
						= beanClass.getAnnotation(RequestMapping.class);
				// 控制器路径
				String controllerPath = controllerRequestMapping.value();
				
				for (Method declaredMethod : beanClass.getDeclaredMethods()) {
					if (declaredMethod.isAnnotationPresent(RequestMapping.class)) {
						RequestMapping methodRequestMapping
								= declaredMethod.getAnnotation(RequestMapping.class);
						
						String handlerPath = methodRequestMapping.value();
						
						Handler handler = new Handler(beanInstance, declaredMethod,
								Pattern.compile(controllerPath + handlerPath));
						Map<String, Integer> paramIndexMap = handler.getParamIndexMap();
						
						// 处理器方法的参数
						Parameter[] parameters = declaredMethod.getParameters();
						for (int i = 0; i < parameters.length; i++) {
							Class<?> parameterClass = parameters[i].getType();
							if (parameterClass == HttpServletRequest.class || parameterClass == HttpServletResponse.class) {
								paramIndexMap.put(parameterClass.getSimpleName(), i);
							} else {
								paramIndexMap.put(parameters[i].getName(), i);
							}
						}
						
						handlerList.add(handler);
					}
				}
			}
		}
	}
	
	/**
	 * 依赖注入
	 *
	 * @throws IllegalAccessException
	 */
	private void doAutowired() throws IllegalAccessException {
		if (beans.isEmpty()) {
			return;
		}
		
		for (Map.Entry<String, Object> entry : beans.entrySet()) {
			String beanName = entry.getKey();
			Object beanInstance = entry.getValue();
			
			for (Field declaredField : beanInstance.getClass().getDeclaredFields()) {
				if (declaredField.isAnnotationPresent(Autowired.class)) {
					Autowired autowired = declaredField.getAnnotation(Autowired.class);
					String propertyName = autowired.value();
					if ("".equals(propertyName.trim())) {
						propertyName = declaredField.getType().getSimpleName();
						// fixed:未见bean id转为首字母小写,导致注入失败
						propertyName = lowerFirst(propertyName);
					}
					
					Object propertyValue = beans.get(propertyName);
					declaredField.setAccessible(true);
					declaredField.set(beanInstance, propertyValue);
				}
			}
		}
	}
	
	/**
	 * 根据获得的类名完成bean的实例化
	 * 根据注解属性值或类名首字母小写作为key,将实例化的bean存入map
	 */
	private void doInit() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException,
			InvocationTargetException, InstantiationException {
		for (String classType : classTypeList) {
			Class<?> componentClass = Class.forName(classType);
			// 匹配@Controller和@Service注解
			Object instance = null;
			String componentName = null;
			if (componentClass.isAnnotationPresent(Controller.class)) {
				instance = componentClass.getConstructor().newInstance();
				Controller controller = componentClass.getAnnotation(Controller.class);
				componentName = controller.value();
				if ("".equals(componentName)) {
					// fixed:componentClass.getName()获取的是全限定类名,导致在di时获取不到已经实例化的bean
					String className = componentClass.getSimpleName();
					componentName = lowerFirst(className);
				}
				
				beans.put(componentName, instance);
				
			} else if (componentClass.isAnnotationPresent(Service.class)) {
				instance = componentClass.getConstructor().newInstance();
				Service service = componentClass.getAnnotation(Service.class);
				componentName = service.value();
				if ("".equals(componentName)) {
					String className = componentClass.getSimpleName();
					componentName = lowerFirst(className);
				}
				
				beans.put(componentName, instance);
				
				// 如果当前bean实现了接口,则以接口名为key保存一份实例
				// service层通常都是接口实现类
				Class<?>[] interfaces = componentClass.getInterfaces();
				for (Class<?> anInterface : interfaces) {
					String interfaceName = anInterface.getSimpleName();
					interfaceName = lowerFirst(interfaceName);
					beans.put(interfaceName, instance);
				}
			} else {
				continue;
			}
		}
	}
	
	/**
	 * 完成大小写转换
	 *
	 * @param className
	 * @return
	 */
	private String lowerFirst(String className) {
		char[] chars = className.toCharArray();
		int firstChar = chars[0];
		chars[0] = ((char) (firstChar + 32));
		
		return String.valueOf(chars);
	}
	
	/**
	 * 根据包路径扫描对应注解类(@Controller,@Service)
	 * 将包转换为目录，根据文件类型和文件后缀找到class文件的文件名，再与入参包路径拼接得到Class全路径名
	 *
	 * @param basePackage 需要扫描的包名
	 * @throws Exception
	 */
	private void doPackageScan(String basePackage) throws Exception {
		if (basePackage == null || basePackage.length() == 0) {
			throw new RuntimeException("check your config first!");
		}
		// 将包结构 xxx.xxx.xxx 转换成路径结构 xxx/xxx/xxx
		String basePath = basePackage.replace(".", "/");
		// 程序运行的根目录
		URL rootUrl = Thread.currentThread().getContextClassLoader().getResource("");
		// 根据包名转换得到的决定路径
		String rootPath = "";
		if (rootUrl != null) {
			rootPath = rootUrl.getPath();
		}
		// 被扫描包的全限定性路径
		rootPath = rootPath + "/" + basePath;
		
		File packageFile = new File(rootPath);
		
		if ((!packageFile.exists())) {
			throw new RuntimeException("wrong basePackage:" + basePackage);
		}
		
		File[] files = packageFile.listFiles();
		if (files != null && files.length > 0) {
			for (File file : files) {
				String fileName = file.getName();
				
				if (file.isDirectory()) {
					doPackageScan(basePackage + "." + fileName);
				} else {
					int index = fileName.lastIndexOf(".class");
					if (index != -1) {
						String simpleClassName = fileName.substring(0, index);
						String fullClassName = basePackage + "." + simpleClassName;
						classTypeList.add(fullClassName);
					}
				}
			}
		}
	}
	
	/**
	 * 配置文件加载
	 *
	 * @param contextConfigLocation
	 */
	private void doLoadConfig(String contextConfigLocation) throws IOException {
		InputStream inputStream =
				DispatcherServlet.class.getClassLoader().getResourceAsStream(contextConfigLocation);
		
		properties.load(inputStream);
	}
	
	@Override
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,
			IOException {
		doPost(request, response);
	}
	
	@Override
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException,
			IOException {
		LOGGER.info("begin handle request --------->");
		
		
		Handler handler = getHandler(request);
		PrintWriter writer = response.getWriter();
		if (handler == null) {
			writer.println("404!...bad request!");
		} else {
			/*
			 * 参数绑定
			 * 1.初始化一个长度和处理器方法参数个数相等的数组,用于执行处理器方法的入参:method.invoke(obj,args)
			 * 2.获取httpServletRequest里封装好的请求参数集,如果参数存在于处理器方法参数map中,则存入参数数组
			 * 3.判断HttpServletRequest和HttpServletResponse是否为处理器方法入参,是的话就存入数组
			 */
			@NonNull Object controller = handler.getController();
			@NonNull Method method = handler.getMethod();
			Object[] args = new Object[method.getParameters().length];
			Map<String, Integer> paramIndexMap = handler.getParamIndexMap();
			
			Map<String, String[]> parameterMap = request.getParameterMap();
			for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
				String parameterName = entry.getKey();
				// 如果参数名存在于处理器参数map中,则存入数组
				if (parameterMap.containsKey(parameterName)) {
					String[] entryValue = entry.getValue();
					// 将数组用.拼接成字符串
					String join = StrUtil.join(".", entryValue);
					Integer integer = paramIndexMap.get(parameterName);
					
					args[integer] = join;
				}
			}
			
			// 注:如果通过形参去获取类对象的名称,则得到的是实现类的类名:RequestFacade和ResponseFacade
			String httpServletRequestName = HttpServletRequest.class.getSimpleName();
			String httpServletResponseName = HttpServletResponse.class.getSimpleName();
			
			if (paramIndexMap.containsKey(httpServletRequestName)) {
				Integer requestIndex = paramIndexMap.get(httpServletRequestName);
				args[requestIndex] = request;
			}
			
			if (paramIndexMap.containsKey(httpServletResponseName)) {
				Integer responseIndex = paramIndexMap.get(httpServletResponseName);
				args[responseIndex] = response;
			}
			
			try {
				if (!checkAuthorityForUser(handler, args)) {
					writer.println("sorry! you have no authority for this!");
					return;
				}
				Object result = method.invoke(controller, args);
				writer.println(((String) result));
				
			} catch (IllegalAccessException | InvocationTargetException e) {
				e.printStackTrace();
			}
			
		}
		
	}
	
	private Handler getHandler(HttpServletRequest request) {
		String requestURI = request.getRequestURI();
		LOGGER.info("requestURI=" + requestURI);
		
		for (Handler handler : handlerList) {
			// uri匹配
			if (handler.getUrlPattern().matcher(requestURI).matches()) {
				LOGGER.info("匹配到的handler:" + handler);
				return handler;
			}
		}
		
		return null;
	}
	
	/**
	 * 验证指定用户是否有方法handler权限
	 * 规则：
	 * 如果控制器上有@Security注解，先根据注解配置的用户进行第一层过滤，未匹配第一层过滤的用户无当前控制器下所有处理器方法的执行权限；
	 * 再根据处理器方法有无该注解进行二次过滤
	 * 如果控制器上无@Security注解，处理器方法有该注解，则根据处理器方法上的注解属性进行用户过滤
	 *
	 * @return boolean
	 */
	private boolean checkAuthorityForUser(Handler handler, Object... args) {
		@NonNull Object controller = handler.getController();
		@NonNull Method method = handler.getMethod();
		
		if (controller.getClass().isAnnotationPresent(Security.class)) {
			Security securityOnController = controller.getClass().getDeclaredAnnotation(Security.class);
			String[] usernamesOnController = securityOnController.value();
			Integer index = handler.getParamIndexMap().get("username");
			if (index == null) {
				return false;
			}
			
			if (ArrayUtil.contains(usernamesOnController, args[index])) {
				// 获取处理器方法上@Security注解
				if (method.isAnnotationPresent(Security.class)) {
					Security securityOnMethod = method.getDeclaredAnnotation(Security.class);
					String[] usernamesOnMethod = securityOnMethod.value();
					
					return ArrayUtil.contains(usernamesOnMethod, args[index]);
				}
				return true;
			}
			return false;
		}
		return false;
	}
}
